﻿using System;
using System.Data;
using System.Reflection;
using System.Collections.Generic;
using Light.Utility;

namespace Light.Framework
{
    /// <summary>
    /// 获取类上的数据库映射信息辅助类
    /// </summary>
    public class DbMapper
    {
        /// <summary>
        /// 表别名前缀
        /// </summary>
        public const string PREFIX_TABLE_ALIAS = "t";

        /// <summary>
        /// 主表别名
        /// </summary>
        public const string PRIMARY_TABLE_ALIAS = PREFIX_TABLE_ALIAS + "0";

        /// <summary>
        ///  获取类映射的数据库表名
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        public static string GetTableName<T>()
        {
            return GetTableName(typeof(T));
        }

        /// <summary>
        ///  获取类映射的数据库表名
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        public static string GetTableName(Type type)
        {
            string tableName = ReflectionHelper.GetAttributeValue<TableAttribute>(type, "Name");
            if (String.IsNullOrEmpty(tableName))
                tableName = type.Name;
            return tableName;
        }

        /// <summary>
        /// 从类中找到映射了主键的信息
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        public static ColumnInfo GetPrimaryKey<T>()
        {
            return GetPrimaryKey(typeof(T));
        }

        /// <summary>
        /// 获取类中标记为主键的列信息
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        public static ColumnInfo GetPrimaryKey(Type type)
        {
            ColumnInfo column = null;
            PropertyInfo[] properties = type.GetProperties();
            foreach (PropertyInfo p in properties)
            {
                if (ReflectionHelper.HasAttribute<PrimaryKeyAttribute>(p))
                {
                    column = DbMapper.GetPrimaryKey(p);
                    break;
                }
            }
            return column;
        }

        /// <summary>
        /// 获取主键尚未保存时主键的值
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        public static string GetUnsavedValue(Type type)
        {
            ColumnInfo c = GetPrimaryKey(type);
            return c.UnsavedValue;
        }

        /// <summary>
        /// 获取类中某个属性对应的数据库字段信息
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="pName"></param>
        /// <returns></returns>
        public static ColumnInfo GetColumn<T>(string pName)
        {
            PropertyInfo p = typeof(T).GetProperty(pName);
            return GetColumn(p, 0);
        }

        /// <summary>
        /// 获取类型中所有属性映射的数据库字段信息
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        public static List<ColumnInfo> GetColumns<T>()
        {
            return GetColumns(typeof(T), 0);
        }

        /// <summary>
        /// 获取类型中所有属性映射的数据库字段信息
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        public static List<ColumnInfo> GetColumns(Type type, int cascade)
        {
            string key = type + "_columns_list_" + cascade;
            WebCache cache = new WebCache();
            List<ColumnInfo> columns = cache.Get(key) as List<ColumnInfo>;
            if (columns == null)
            {
                columns = new List<ColumnInfo>();
                PropertyInfo[] properties = type.GetProperties();
                foreach (PropertyInfo p in properties)
                {
                    if (ReflectionHelper.HasAttribute<PrimaryKeyAttribute>(p))
                    {
                        ColumnInfo column = GetPrimaryKey(p);
                        if (column != null)
                            columns.Add(column);
                    }
                    else
                    {
                        ColumnInfo field = GetColumn(p, cascade);
                        if (field != null)
                            columns.Add(field);
                    }
                }
                cache.Set(key, columns); //加入缓存，避免多次调用反射，损耗性能
            }
            return columns;
        }


        /// <summary>
        /// 获取类型中所有属性映射的数据库字段信息
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        public static Dictionary<string, ColumnInfo> GetColumnsDic<T>()
        {
            string key = typeof(T) + "_columns_dic";
            WebCache cache = new WebCache();
            Dictionary<string, ColumnInfo> dic = cache.Get(key) as Dictionary<string, ColumnInfo>;
            if (dic == null)
            {
                dic = GetColumnsDic(typeof(T));
                cache.Set(key, dic); //加入缓存，避免多次调用反射，损耗性能
            }
            return dic;
        }

        /// <summary>
        /// 获取类型中所有属性映射的数据库字段信息
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        public static Dictionary<string, ColumnInfo> GetColumnsDic(Type type)
        {
            Dictionary<string, ColumnInfo> columns = new Dictionary<string, ColumnInfo>();
            PropertyInfo[] properties = type.GetProperties();
            foreach (PropertyInfo p in properties)
            {
                if (ReflectionHelper.HasAttribute<PrimaryKeyAttribute>(p))
                {
                    ColumnInfo column = GetPrimaryKey(p);
                    if (column != null)
                        columns.Add(column.PropertyName, column);
                }
                else
                {
                    ColumnInfo column = GetColumn(p, 0);
                    if (column != null)
                        columns.Add(column.PropertyName, column);
                }
            }
            return columns;
        }



        /// <summary>
        /// 获取类型中所有属性映射的数据库关联（如外键）字段信息
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        public static Dictionary<string, BelongsInfo> GetBelongsColumnsDic<T>()
        {
            string key = typeof(T) + "_belongsColumns_dic";
            WebCache cache = new WebCache();
            Dictionary<string, BelongsInfo> dic = cache.Get(key) as Dictionary<string, BelongsInfo>;
            if (dic == null)
            {
                dic = GetBelongsColumnsDic(typeof(T));
                cache.Set(key, dic); //加入缓存，避免多次调用反射，损耗性能
            }
            return dic;
        }

        /// <summary>
        /// 已递归深度，递归一次+1
        /// </summary>
        static int deep = 0;

        /// <summary>
        /// 获取类型中所有属性映射的数据库关联（如外键）字段信息
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        public static Dictionary<string, BelongsInfo> GetBelongsColumnsDic(Type type)
        {
            //重置深度
            deep = 0;
            return GetBelongsColumnsDic(type, null);
        }

        /// <summary>
        /// 获取关联类的主键值
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="obj"></param>
        /// <param name="updateColumn">修改更新的属性</param>
        /// <returns></returns>
        public static Dictionary<string, object> GetBelongsKeyValue<T>(T obj, UpdateColumn updateColumn = null)
        {
            Dictionary<string, object> dic = new Dictionary<string, object>();
            List<BelongsInfo> columns = GetBelongsColumns<T>();
            foreach (var column in columns)
            {
                if (column.ReadOnly || (updateColumn != null && !updateColumn.Include(column.PropertyName)))
                    continue;

                object belongsID = null;
                object belongsObj = ReflectionHelper.GetPropertyValue(obj, column.PropertyName);
                if (belongsObj != null)
                {
                    ColumnInfo primaryKey = DbMapper.GetPrimaryKey(column.Type);
                    belongsID = ReflectionHelper.GetPropertyValue(belongsObj, primaryKey.PropertyName);
                }
                dic.Add(column.Name, belongsID);
            }
            return dic;
        }

        /// <summary>
        /// 获取1对多列映射信息
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        public static Dictionary<string, HasManyInfo> GetHasMany(Type type)
        {
            string key = type.Name + "_hasmany_dic";
            WebCache cache = new WebCache();
            Dictionary<string, HasManyInfo> dic = cache.Get(key) as Dictionary<string, HasManyInfo>;

            if (dic == null)
            {
                dic = new Dictionary<string, HasManyInfo>();

                PropertyInfo[] pInfos = type.GetProperties();
                foreach (var p in pInfos)
                {
                    object[] attrs = p.GetCustomAttributes(typeof(HasManyAttribute), true);
                    bool mapped = attrs != null && attrs.Length > 0;

                    if (mapped)
                    {
                        HasManyInfo obj = new HasManyInfo();

                        HasManyAttribute attr = (HasManyAttribute)attrs[0];
                        //Type genericType = p.PropertyType.GetGenericArguments()[0];
                        obj.Type = attr.Type;
                        obj.ColumnKey = attr.ColumnKey;
                        obj.Alias = p.Name;
                        obj.PropertyName = p.Name;

                        dic.Add(p.Name, obj);
                    }
                }
                cache.Set(key, dic);
            }
            return dic;
        }

        /// <summary>
        /// 获取多对多列映射信息
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        public static Dictionary<string, ManyToManyInfo> GetManyToMany(Type type)
        {
            string key = type.Name + "_manytomany_dic";
            WebCache cache = new WebCache();
            Dictionary<string, ManyToManyInfo> dic = cache.Get(key) as Dictionary<string, ManyToManyInfo>;

            if (dic == null)
            {
                dic = new Dictionary<string, ManyToManyInfo>();

                PropertyInfo[] pInfos = type.GetProperties();
                foreach (var p in pInfos)
                {
                    object[] attrs = p.GetCustomAttributes(typeof(ManyToManyAttribute), true);
                    bool mapped = attrs != null && attrs.Length > 0;

                    if (mapped)
                    {
                        ManyToManyInfo obj = new ManyToManyInfo();

                        ManyToManyAttribute attr = (ManyToManyAttribute)attrs[0];
                        //Type genericType = p.PropertyType.GetGenericArguments()[0];
                        obj.Type = attr.Type;
                        obj.ColumnKey = attr.ColumnKey;
                        obj.ColumnRef = attr.ColumnRef;
                        obj.Table = attr.Table;
                        obj.Alias = attr.Table;
                        obj.PropertyName = p.Name;

                        dic.Add(p.Name, obj);
                    }
                }

                cache.Set(key, dic);
            }
            return dic;
        }

        #region 私有方法

        /// <summary>
        /// 获取属性对应的列表
        /// </summary>
        /// <param name="type"></param>
        /// <param name="propertyName"></param>
        /// <returns></returns>
        public static string GetColumnName(Type type, string propertyName)
        {
            string columnName = string.Empty;
            string aliasStr = string.Empty;

            Dictionary<string, ColumnInfo> dicColumns = GetColumnsDic(type);
            Dictionary<string, BelongsInfo> dicBelongs = GetBelongsColumnsDic(type);

            if (dicBelongs != null && dicBelongs.Count > 0)
            {
                aliasStr = DbMapper.PRIMARY_TABLE_ALIAS + ".";
            }
            if (dicColumns.ContainsKey(propertyName))
            {
                columnName = (aliasStr + dicColumns[propertyName].Name);
            }
            else if (dicBelongs.ContainsKey(propertyName))
            {
                BelongsInfo bc = dicBelongs[propertyName]; //找到对应的外键信息
                columnName = (DbMapper.PRIMARY_TABLE_ALIAS + "." + bc.Name);
            }
            else if (propertyName.Contains("."))
            {
                string[] temp = propertyName.Split('.'); // Aaa.Bbb.Ccc => Aaa.Bbb、Ccc
                string lastPName = temp[temp.Length - 1]; //Ccc
                string firstPName = propertyName.Substring(0, propertyName.LastIndexOf(".")); //Aaa.Bbb

                if (dicBelongs.ContainsKey(firstPName))
                {
                    BelongsInfo bc = dicBelongs[firstPName];  //找到对应的外键信息（表别名）

                    //找到外键表的列
                    int cascade = propertyName.Split('.').Length;
                    List<ColumnInfo> bcColumns = DbMapper.GetColumns(bc.Type, cascade);
                    ColumnInfo bcColumn = bcColumns.Find(c => c.PropertyName == lastPName);
                    if (bcColumn != null)
                        columnName = (bc.Alias + "." + bcColumn.Name);
                    else
                        throw new Exception("不支持按 " + propertyName + " 查询");
                }
            }
            if (String.IsNullOrEmpty(columnName))
            {
                columnName = propertyName;
            }
            return columnName;
        }

        private static ColumnInfo GetColumn(PropertyInfo p, int cascade)
        {
            ColumnInfo column = null;

            Type pType = p.PropertyType;
            object[] attrs = p.GetCustomAttributes(typeof(ColumnAttribute), true);
            bool mapped = attrs != null && attrs.Length > 0;

            if (mapped)
            {
                ColumnAttribute attr = (ColumnAttribute)attrs[0];
                if (attr.Casade >= cascade)
                {
                    column = new ColumnInfo();

                    column.PropertyName = p.Name;
                    column.Name = attr.Name;
                    column.Type = attr.Type;
                    column.Size = attr.Size;
                    column.ReadOnly = attr.ReadOnly;
                    column.IsAutoIncrement = attr.IsAutoIncrement;

                    #region  如果没有显示指定，则取默认值

                    if (String.IsNullOrEmpty(column.Name)) //如果没有指定字段名，则取属性名
                    {
                        column.Name = p.Name;
                    }
                    if (column.Type == DbType.Object) //如果没有指定字段类型，则根据属性类型转换
                    {
                        string pTypeName = pType.Name.ToLower();
                        switch (pTypeName)
                        {
                            case "int16":
                            case "int32":
                                column.Type = DbType.Int32;
                                break;
                            case "double":
                            case "decimal":
                                column.Type = DbType.Decimal;
                                break;
                            case "datetime":
                                column.Type = DbType.DateTime;
                                break;
                            case "boolean":
                                column.Type = DbType.Boolean;
                                break;
                            case "string":
                            default:
                                column.Type = DbType.String;
                                break;
                        }
                        if (pType.UnderlyingSystemType.FullName.ToLower().Contains("datetime"))
                        {
                            column.Type = DbType.DateTime;
                        }
                    }
                    #endregion  如果没有显示指定，则取默认值
                }
            }
            return column;
        }

        private static ColumnInfo GetPrimaryKey(PropertyInfo p)
        {
            Type pType = p.PropertyType;
            object[] attrs = p.GetCustomAttributes(typeof(PrimaryKeyAttribute), true);
            bool mapped = attrs != null && attrs.Length > 0;

            if (!mapped)
            {
                return null;
            }
            else
            {
                ColumnInfo column = new ColumnInfo();
                PrimaryKeyAttribute attr = (PrimaryKeyAttribute)attrs[0];

                column.UnsavedValue = attr.UnsavedValue;
                column.PropertyName = p.Name;
                column.Name = attr.Name;
                column.Type = attr.Type;
                column.Size = attr.Size;
                column.ReadOnly = false;
                column.IsAutoIncrement = attr.IsAutoIncrement;

                #region  如果没有显示指定，则取默认值

                if (String.IsNullOrEmpty(column.Name)) //如果没有指定字段名，则取属性名
                {
                    column.Name = p.Name;
                }
                if (column.Type == DbType.Object) //如果没有指定字段类型，则根据属性类型转换
                {
                    string pTypeName = pType.Name.ToLower();
                    switch (pTypeName)
                    {
                        case "int16":
                        case "int32":
                            column.Type = DbType.Int32;
                            break;
                        case "string":
                        default:
                            column.Type = DbType.String;
                            break;
                    }
                }
                #endregion  如果没有显示指定，则取默认值

                return column;
            }
        }

        private static BelongsInfo GetBelongs(PropertyInfo p, int idx, BelongsInfo refer)
        {
            Type pType = p.PropertyType;
            object[] attrs = p.GetCustomAttributes(typeof(BelongsAttribute), true);
            bool mapped = attrs != null && attrs.Length > 0;

            if (!mapped)
            {
                return null;
            }
            else
            {
                BelongsInfo column = new BelongsInfo();
                BelongsAttribute attr = (BelongsAttribute)attrs[0];

                column.Name = attr.Name;
                column.Type = p.PropertyType; //如：Mechant
                column.JoinBehaviour = attr.JoinBehaviour;
                column.RecursiveDeep = attr.RecursiveDeep;
                column.ReadOnly = attr.ReadOnly;

                if (refer != null)
                {
                    column.PropertyName = refer.PropertyName + "." + p.Name; //如： Agent.Basic
                    column.AliasReference = refer.Alias;
                    column.Alias = refer.Alias + "_" + PREFIX_TABLE_ALIAS + idx; //如：t1_t0
                }
                else
                {
                    column.PropertyName = p.Name;
                    column.Alias = PREFIX_TABLE_ALIAS + (idx + 1);
                }

                return column;
            }
        }

        /// <summary>
        /// 递归获取外键关联信息
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        private static Dictionary<string, BelongsInfo> GetBelongsColumnsDic(Type type, BelongsInfo refer)
        {
            Dictionary<string, BelongsInfo> columns = new Dictionary<string, BelongsInfo>();
            PropertyInfo[] properties = type.GetProperties();
            int idx = 0;
            foreach (PropertyInfo p in properties)
            {
                if (ReflectionHelper.HasAttribute<BelongsAttribute>(p))
                {
                    BelongsInfo column = GetBelongs(p, idx++, refer);
                    if (column != null && !columns.ContainsKey(column.PropertyName))
                    {
                        columns.Add(column.PropertyName, column);

                        //避免无限递归
                        if (column.Type != type || deep < column.RecursiveDeep)
                        {
                            if (column.Type == type)
                                deep++;

                            //递归
                            Dictionary<string, BelongsInfo> subColumns = GetBelongsColumnsDic(column.Type, column);
                            if (subColumns != null && subColumns.Count > 0)
                            {
                                foreach (var key in subColumns.Keys)
                                {
                                    columns.Add(key, subColumns[key]);
                                }
                            }
                        }
                    }
                }
            }
            return columns;
        }

        /// <summary>
        /// 获取类型中所有属性映射的数据库关联（外键）字段信息
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        private static List<BelongsInfo> GetBelongsColumns<T>()
        {
            List<BelongsInfo> columns = new List<BelongsInfo>();
            PropertyInfo[] properties = typeof(T).GetProperties();

            int idx = 0;
            foreach (PropertyInfo p in properties)
            {
                if (ReflectionHelper.HasAttribute<BelongsAttribute>(p))
                {
                    BelongsInfo column = GetBelongs(p, idx++, null);
                    if (column != null)
                        columns.Add(column);
                }
            }
            return columns;
        }

        #endregion
    }
}
